home *** CD-ROM | disk | FTP | other *** search
/ AmigActive 23 / AACD 23.iso / AACD / Utilities / BareED / source / AsyncStartup.c next >
C/C++ Source or Header  |  2000-09-08  |  18KB  |  628 lines

  1. /*
  2.     Start-up-code for BareED
  3.     Written 10 & 11/99 by Jörg van de Loo, Hövel 15, 47559 Kranenburg, FRG.
  4.  
  5.     06 - 08/00 bug fixing, revising and adapting to GNU-C by Gunther Nikl.
  6.     Thanks, Gunther.
  7.  
  8.     This  code-code  is  freeware  (written by J.v.d.Loo and Gunther Nikl) and
  9.     it's  especially  written  in  mind the MaxonC++ compiler V4 and the GNU-C
  10.     compiler V2.95.# (useful for C and ANSI-C and C++ modes).
  11.  
  12.     CLI parser 'borrowed' from AZTEC-C package (modified).
  13.  
  14.     This  start-up-code  set up argc and argv with the command line parameters
  15.     given   through  the  CLI  and  with  all  selected  files  given  through
  16.     Workbench,  e.g. double click on project icon or shift select. Additional,
  17.     it ensures the present of a 68020 processor and Kickstart 2.0 (beta).
  18.  
  19.     No  stdin,  stdout, stderr terminals will be set up since they are useless
  20.     for BareED.
  21.  
  22.     The   variables   SysBase  and  ExecBase  are  only  present  within  this
  23.     start-up-code;  this  ensures  that  the additional code of BareED doesn't
  24.     refer  to  the  variables  set up by the start-up-code. This is due to the
  25.     fact  that  my  compiler  would otherwise address them as 32 bit addresses
  26.     that  will  cause each time a reloc32 hunk entry. What I do is to refer to
  27.     _DOS_Base  and  _Exec_Base  once,  to  set  up  DOSBase and SysBase in the
  28.     additional  code of BareED, so that my compiler will them address from now
  29.     on within BareED indirect to the processor register a4.
  30.  
  31.     When  you  compile  this  start-up-code  ensure  that  no  68020 or higher
  32.     instructions  are  used  before the processor check is executed, otherwise
  33.     on a plain 68000 processor this start-up-code will fail with a GURU.
  34.  
  35.     This  start-up-code  checks  for the amount of free stack, too. Since this
  36.     start-up-code  uses  very less stack (56 bytes when running and only up to
  37.     328  bytes  while  setting  up variables) it should be enough when using a
  38.     stack  size of only 4096 bytes for the load file of BareED, even under 3rd
  39.     party  graphic  emulation  systems  -  which  in fact do require much more
  40.     stack  than the native Amiga OS 3 graphic system (up to 3Kb when BareED is
  41.     running).
  42.  
  43.     One  goal  of  this start-up-code is, that it gains at lot of stack (up to
  44.     1.1  Kb)  compared  to the original start-up-code that comes along with my
  45.     compiler (MaxonC++, Jörg).
  46.     Currently  it  needs  a  given stack size of 4052 bytes when started off a
  47.     CLI surround and 4046 bytes when started off the Workbench.
  48.  
  49.     11/99  -  modified  this  start-up-code to run-back when started off a CLI
  50.     window  or  called  with  RunCommand()  or  within  a  batch  script of an
  51.     application.
  52.  
  53.     06/00 - bug fixing and revising by Gunther Nikl.
  54.     07/00 - Can now be used for StormC; J.v.d.Loo
  55.     07/00 - Compliant to SAS-C; G. Nikl
  56.     08/00 - No forbid of task switching anymore, use of a semaphore lock, G. Nikl.
  57.  
  58.     NOTE: According  to  Gunther, GNU-C 2.95.# allows startup.c/BareED.c to be
  59.           compiled in small (base relative) mode!
  60.  
  61.           I  originally  used  the  GNU-Compiler  that  can  be  found on Geek
  62.           Gadgets II.
  63.           Thanks to Mr. Fish for this compilation.
  64.  
  65.           And  thank  you  Gunther  for  your annotations, although they first
  66.           brought me down.
  67.  
  68.           This start-up code was bug fixed and improved by Gunther Nikl!
  69.  
  70.           Rework for MaconC++ and StormC done by J.v.d.Loo .
  71.  
  72.           NOTE:  The handling of "__inline" in GNU-C / SAS-C does not complain
  73.                  with  legal rules! Thus "__inline" has been removed for Storm
  74.                  and Maxon C/C++ compilers!
  75. */
  76.  
  77.  
  78. #include <exec/execbase.h>
  79. #include <exec/memory.h>
  80.  
  81. #include <dos/dosextens.h>
  82. #include <dos/dostags.h>
  83.  
  84. #include <workbench/startup.h>
  85.  
  86. #include <clib/exec_protos.h>
  87. #include <clib/dos_protos.h>
  88.  
  89. #if !defined(__MAXON__) && !defined(__STORM__)
  90. #include <proto/exec.h>
  91. #include <proto/dos.h>
  92. #else
  93. #include <pragma/exec_lib.h>
  94. #include <pragma/dos_lib.h>
  95. #endif
  96.  
  97. #ifdef __GNUC__
  98.  #define ASM
  99.  #define REG(reg,arg) arg __asm(#reg)
  100. #else
  101.  #if !defined (__MAXON__) 
  102.  #define ASM __asm
  103.  #endif
  104.  #define REG(reg,arg) register __##reg arg
  105. #endif
  106.  
  107. #ifdef __MAXON__
  108.  extern "C" void GetBaseReg( void);
  109.  #define __saveds
  110.  #define ASM
  111. #endif
  112.  
  113. #if defined(__MAXON__) || defined(__STORM__)
  114.   #define __inline
  115.   #define __stdargs
  116.   #ifdef __cplusplus            // Tells if MAXON or STORM compilers run in C++ mode
  117.     #define main main__UjPPUc    // C++ name for main
  118.     extern "ASM" main__UjPPUc( unsigned long argc, unsigned char **argv);    // Real function name and parameters
  119.     #pragma -                    // Turn off C++ compile mode (use ANSI-C instead)
  120.   #endif
  121. #endif
  122.  
  123. struct Message        *WBenchMsg;
  124. struct ExecBase        *_Exec_Base;
  125. struct DosLibrary    *_DOS_Base;
  126.  
  127. static struct ExecBase        *SysBase;    // Allow only to appear within startup.c
  128. static struct DosLibrary    *DOSBase;    // Allow only to appear within startup.c
  129.  
  130. static BPTR _prg_dir    = -1;            // Initial value, no directory remembered
  131. static unsigned int        _argc, _arg_len;
  132. static unsigned char    **_argv, *_arg_lin;
  133. static struct Process    *MasterTask = 0;
  134. static struct SignalSemaphore _AsyncSem;
  135. static short _Kick1;
  136.  
  137.  
  138. #define DOSName            (UBYTE *)"dos.library"
  139. #define WrongCPU        (UBYTE *)"ERROR: CPU < 68020!\n"
  140. #define NotEnoughtMem    (UBYTE *)"ERROR: Not enought memory!\n"
  141. #define NotEnoughtStack    (UBYTE *)"ERROR: Stack < 4096 bytes!\n"
  142. #define WrongLIB        (UBYTE *)"ERROR: Library versions < 36!\n"
  143. #define WrongError        (UBYTE *)"ERROR: Non-describt fault!\n" // ????
  144. #define Console            "CON:0/0/320/80/ERROR REPORT/AUTO/CLOSE/WAIT"
  145.  
  146.  
  147. static void _main_jmp( unsigned long parlen, unsigned char *parameter);
  148. int __stdargs main( unsigned long argc, unsigned char **argv);
  149.  
  150.  
  151. // ################################################################
  152. /* Gunther  has  enhanced  the  start-up  code so that it's now possible (when
  153.    started  off  the  CLI)  that  this  routine is twice called: 1st time when
  154.    initialising;  2nd time, when used as main entry for detached (new created)
  155.    task! */
  156.  
  157. int ASM __saveds INIT_0_run_me_at_first_place( REG(d0,unsigned long parlen), REG(a0,unsigned char *parameter) )
  158. {
  159.     #ifdef __MAXON__        // Since MaxonC/C++ does not understand __saveds
  160.     GetBaseReg();
  161.     #endif
  162.  
  163.     _Exec_Base = SysBase = *((struct ExecBase **) 4);    // From memory location 4 to exec library
  164.  
  165.     _main_jmp( parlen, parameter);    // We only return here if the detach from the CLI was a success!
  166.                                     // We jump to _main_jmp() because if we would do here
  167.                                     // the following stuff, d0 and a0 would ever be pre-
  168.                                     // reserved, which blows up the object file un-necessary
  169.  
  170.     return 0;                        // Return code (for CLI), in case detach was a success!
  171. }
  172.  
  173. // ################################################################
  174.  
  175. /* Following code is to avoid reloc32 entries - since they would otherwise
  176.    (when linked with the object files) called via "jsr" and not via "bsr" */
  177.  
  178. static __inline unsigned int strlenNR( register const unsigned char *str)
  179. {
  180.     register unsigned int i = 0;
  181.  
  182.     while (*str++)
  183.         i++;
  184.     return i;
  185. }
  186.  
  187. static __inline void strncpyNR( register unsigned char *d, register const unsigned char *s, register unsigned int i)
  188. {
  189.     while (i)
  190.     {
  191.         *d++ = *s++;
  192.         i--;
  193.     }
  194.     *d = 0;
  195. }
  196.  
  197. static __inline void strcpyNR( register unsigned char *d, register const unsigned char *s)
  198. {
  199.     while (*s)
  200.         *d++ = *s++;
  201.     *d = 0;
  202. }
  203.  
  204. static __inline void strcatNR( register unsigned char *d, register const unsigned char *s)
  205. {
  206.     while (*d)
  207.         d++;
  208.     
  209.     while( *s)
  210.         *d++ = *s++;
  211.     *d = 0;
  212. }
  213.  
  214. static __inline void strncatNR( register unsigned char *d, register const unsigned char *s, register unsigned int i)
  215. {
  216.     while (*d)
  217.         d++;
  218.     
  219.     while( i)
  220.     {
  221.         *d++ = *s++;
  222.         i--;
  223.     }
  224.     *d = 0;
  225. }
  226.  
  227. // ################################################################
  228.  
  229. /* Function required by GNU-C linker, not necessary anymore - since
  230.    all work already done by this start-up-code! */
  231.  
  232. #ifdef __GNUC__
  233. void __main(void)
  234. {
  235. }
  236. #endif
  237.  
  238. // ################################################################
  239.  
  240. /* Taken without permission from the AZTEC-package, which in fact
  241.    can be found in books and magazines, too */
  242.  
  243. static void _cli_parse(struct Process *pp, unsigned long alen, register unsigned char *aptr)
  244. {
  245.     register unsigned char *cp;
  246.     register struct CommandLineInterface *cli;
  247.     register unsigned char c;
  248.  
  249.     cli = (struct CommandLineInterface *) BADDR( pp->pr_CLI );
  250.     cp = (unsigned char *) BADDR( cli->cli_CommandName );
  251.  
  252.     _arg_len = (unsigned char) cp[0] + alen + 2;
  253.  
  254.     if ( (_arg_lin = (unsigned char *) AllocMem( ((_arg_len + 7) & -8), MEMF_CLEAR ) ) == 0)
  255.         return;
  256.  
  257.     c = cp[0];
  258.     strncpyNR( _arg_lin, cp + 1, c);
  259.     _arg_lin[c] = ' ';
  260.     _arg_lin[c + 1] = 0;
  261.     strncatNR( _arg_lin, aptr, alen);
  262.     _arg_lin[c] = 0;
  263.  
  264.     for (_argc = 1, aptr = cp = _arg_lin + c + 1; ; _argc++)
  265.     {
  266.         while ( (c = *cp) == ' ' || c == '\t' || c == '\f' || c == '\r' || c == '\n')
  267.             cp++;
  268.         if (*cp < ' ')
  269.             break;
  270.         if (*cp == '"')
  271.         {
  272.             cp++;
  273.             while ( (c = *cp++) )
  274.             {
  275.                 *aptr++ = c;
  276.                 if (c == '"')
  277.                 {
  278.                     if (*cp == '"')
  279.                     {
  280.                         cp++;
  281.                     }
  282.                     else
  283.                     {
  284.                         aptr[-1] = 0;
  285.                         break;
  286.                     }
  287.                 }
  288.             }
  289.         }
  290.         else
  291.         {
  292.             while ( (c = *cp++) && c != ' ' && c != '\t' && c != '\f' && c != '\r' && c != '\n')
  293.                 *aptr++ = c;
  294.             *aptr++ = 0;
  295.         }
  296.         if (c == 0)
  297.             --cp;
  298.     }
  299.  
  300.     *aptr = 0;
  301.     if ( (_argv = (unsigned char **) AllocMem( (((_argc + 1) * 4 + 7) & -8 ), MEMF_CLEAR ) ) == 0 )
  302.     {
  303.         _argc = 0;
  304.         return;
  305.     }
  306.  
  307.     for (c=0, cp=_arg_lin; c < _argc; c++)
  308.     {
  309.         _argv[c] = cp;
  310.         cp += strlenNR( cp) + 1;
  311.     }
  312.  
  313.     _argv[c] = 0;
  314. }
  315.  
  316. // ################################################################
  317.  
  318. /* Allows several Workbench passed in arguments for BareED */
  319.  
  320. static void _wb_parse( struct WBStartup *msg)
  321. {
  322.     int numargs, len, i;
  323.     char str[256], *curr;    // Normally I'm against arrays on stack, but since we're
  324.                             // at front of a load file it doesn't matter (because there
  325.                             // is enough free stack available) and we can here avoid
  326.                             // memory fragmentation through AllocMem()
  327.  
  328.     numargs = msg -> sm_NumArgs; 
  329.     _arg_len = (numargs + 2) * 4;    // Number of arguments into amount bytes (plus 2 long words)
  330.  
  331.     /* Get length in bytes of all arguments including zero bytes and drawer terminators */
  332.     i = 0;
  333.     while (i < numargs)
  334.     {
  335.         if (msg -> sm_ArgList[i] . wa_Lock)
  336.             NameFromLock( msg -> sm_ArgList[i] . wa_Lock, &str[0], 255);
  337.         _arg_len += strlenNR( &str[0]);
  338.         _arg_len += 2; // For zero byte and perhaps for drawer terminator "/" !
  339.         _arg_len += strlenNR( msg -> sm_ArgList[i] . wa_Name);
  340.         i++;
  341.     }
  342.  
  343.     /* Allocate needed space for strings */
  344.     _arg_lin = (unsigned char *) AllocMem( ((_arg_len + 7) & -8), MEMF_CLEAR );
  345.     if ( !_arg_lin)
  346.         return;
  347.  
  348.     _argc = numargs;
  349.     _argv = (unsigned char **) _arg_lin;
  350.     curr = _arg_lin + ((numargs + 2) * 4);
  351.  
  352.     /* Create and copy drawer and filenames into allocated memory; behind the argument pointers! */
  353.     i = 0;
  354.     while (i < numargs)
  355.     {
  356.         _argv[i] = curr;
  357.         if (msg -> sm_ArgList[i] . wa_Lock)
  358.         {
  359.             NameFromLock( msg -> sm_ArgList[i] . wa_Lock, &str[0], 255);
  360.             strcpyNR( curr, &str[0]);
  361.             len = strlenNR( curr);
  362.  
  363.             if ( curr[ len - 1] != ':')
  364.             {
  365.                 curr[len] = '/';
  366.                 len ++;
  367.             }
  368.         }
  369.         else
  370.         {
  371.             len = 0;
  372.         }
  373.  
  374.         strcpyNR( curr + len, msg -> sm_ArgList[i] . wa_Name);
  375.         len = strlenNR( curr);
  376.  
  377.         curr += len + 1;    // Behind zero byte
  378.         i++;                // Next arg
  379.     }
  380.  
  381.     _prg_dir = CurrentDir( msg -> sm_ArgList[0] . wa_Lock);        // Set up "progdir:"
  382. }
  383.  
  384.  
  385. // ################################################################
  386.  
  387. /* Print error code down to a console window, if there isn't one yet,
  388.    open one and give the message. */
  389.  
  390. static __inline void GiveFault( int error)
  391. {
  392.     if (_DOS_Base)        // DOSBase set up?
  393.     {
  394.         static char console[] = Console;
  395.         unsigned char *errorTxt = 0;
  396.         BPTR newStdOut;
  397.  
  398.         if (_Kick1)                // OS 2.0 ?
  399.             console[27] = 0;    // No, OS 1.x, so remove AUTO/CLOSE/WAIT from console description
  400.  
  401.         newStdOut = Open( WBenchMsg ? console : "*", MODE_NEWFILE);    // Figure out if we have to open a console window
  402.         if (newStdOut)                                                // on our own - in case we've been fired up by Workbench
  403.         {
  404.             if (error == 105)
  405.                 errorTxt = WrongCPU;
  406.             if (error == 122)
  407.                 errorTxt = WrongLIB;
  408.             if (error == 217)
  409.                 errorTxt = NotEnoughtStack;
  410.             if (error == 103)
  411.                 errorTxt = NotEnoughtMem;
  412.             if (errorTxt == 0)
  413.                 errorTxt = WrongError;    // ????
  414.  
  415.             Write( newStdOut, errorTxt, strlenNR( errorTxt) );
  416.  
  417.             if (WBenchMsg && _Kick1)    // If WB-start and OS 1.x
  418.                 Delay( 5*60);            // Wait a while
  419.  
  420.             Close( newStdOut);            // Close console
  421.         }
  422.     }
  423. }
  424.  
  425. // ################################################################
  426.  
  427. /* Code to get program's return address and to call it */
  428.  
  429. static const unsigned short _finally_code[] =
  430. {
  431.  0x2400,            // move.l D0,D2                        Save error code
  432.  0x93C9,            // suba.l A1,A1
  433.  0x2C78,0x0004,        // movea.l (4).w,A6
  434.  0x4EAE,0xFEDA,        // jsr _LVOFindTask(A6)                Own task
  435.  0x2040,            // movea.l D0,A0
  436.  0x2068,0x00B0,        // movea.l pr_ReturnAddr(A0),A0        Get system's exit address for us
  437.  0x4FE8,0xFFFC,        // lea -4(A0),sp                    Restore initial stack
  438.  0x2002,            // move.l D2,D0                        Error code to D0
  439.  0x4E75                // rts                                Back to system
  440. };
  441.  
  442. // ################################################################
  443.  
  444. /* Since my compiler doesn't allow to overload a function (stub),
  445.    I use a different name: exitNR ! */
  446.  
  447. void __saveds exitNR( int error)
  448. {
  449.     void (* ASM _finally)( REG(d0,int err) ) = (void (*ASM) ( REG( d0,int)) ) _finally_code;
  450.  
  451.     #ifdef __MAXON__
  452.     GetBaseReg();
  453.     #endif
  454.  
  455.     if (error)
  456.         GiveFault( error);
  457.  
  458.     ((struct Process *) FindTask( NULL)) -> pr_Result2 = error;
  459.  
  460.     if (WBenchMsg)
  461.     {
  462.         if (_arg_lin)
  463.         {
  464.             FreeMem( _arg_lin, ((_arg_len + 7) & -8) );
  465.             if (_prg_dir != -1)            // If it's the same as initial, ignore
  466.                 CurrentDir( _prg_dir);    // Else, lock of basic directory
  467.         }
  468.         Forbid();
  469.         ReplyMsg( WBenchMsg);
  470.     }
  471.     else
  472.     {
  473.         if (_arg_lin)
  474.         {
  475.             FreeMem(_argv, (((_argc + 1) * 4 + 7) & -8) );
  476.             FreeMem(_arg_lin, ((_arg_len + 7) & -8) );
  477.         }
  478.     }
  479.  
  480.     if (_DOS_Base)
  481.         CloseLibrary( (struct Library *) _DOS_Base);
  482.  
  483.     _finally( error);
  484. }
  485.  
  486. // ################################################################
  487.  
  488. /* This function does not return to the caller ! */
  489.  
  490. static void CallMainPRG( void)
  491. {
  492.     exitNR( main( _argc, _argv));
  493. }
  494.  
  495. // ################################################################
  496.  
  497. /* Set up necessary things to provide an auto-detach (RunBack mode).
  498.    This is a little tricky one, which cares about the priority and
  499.    stack size of the original process, which is the setting for the
  500.    new created.
  501.  
  502.    Enhanced by G. Nikl (06/00)
  503. */
  504.  
  505. static APTR LaunchStartup( void)
  506. {
  507.     struct CommandLineInterface *cli;
  508.     struct Process *pr;
  509.     unsigned char *cp;
  510.     struct TagItem tag[5];
  511.     char buf[256];
  512.  
  513.     pr = (struct Process *) FindTask( NULL);
  514.  
  515.     cli = (struct CommandLineInterface *) BADDR( pr->pr_CLI);
  516.     cp = (unsigned char *) BADDR( cli->cli_CommandName);
  517.  
  518.     // Duplicate name: slave task's name equal to master's
  519.     strncpyNR( buf, cp + 1, cp[0]);
  520.  
  521.     // Settings for task to create
  522.     tag[0].ti_Tag = NP_Seglist;
  523.     tag[0].ti_Data = (ULONG) cli->cli_Module;
  524.     tag[1].ti_Tag = NP_FreeSeglist;
  525.     tag[1].ti_Data = TRUE;
  526.     tag[2].ti_Tag = NP_Name;
  527.     tag[2].ti_Data = (ULONG) buf;
  528.     tag[3].ti_Tag = NP_StackSize;
  529.     tag[3].ti_Data = *(ULONG *) pr->pr_ReturnAddr;
  530.     tag[4].ti_Tag = TAG_DONE;
  531.  
  532.     // 08/00 G. Nikl
  533.     InitSemaphore(&_AsyncSem);
  534.  
  535.     // 08/00 G. Nikl
  536.     ObtainSemaphore(&_AsyncSem);
  537.  
  538.     // Create slave task which becomes master task
  539.     if ( (MasterTask = CreateNewProc( tag)) )    // Process created ?
  540.     {
  541.         cli->cli_Module = 0;
  542.     }
  543.  
  544.     // 08/00 G. Nikl
  545.     ReleaseSemaphore(&_AsyncSem);
  546.  
  547.     // Return whether success or not...
  548.     return MasterTask;
  549. }
  550.  
  551. // ################################################################
  552.  
  553. /* This function can be twice called in case we've started from the
  554.    CLI:
  555.    First, when we fired up regularly through the user (MasterTask == 0);
  556.    when we have called the second time (MasterTask != 0), we are the
  557.    through LauchStartup() created task!
  558.  
  559.    Enhanced by G. Nikl (08/00)
  560. */
  561.  
  562. static void _main_jmp( unsigned long parlen, unsigned char *parameter)
  563. {
  564.     if ( !MasterTask)    // First time fired up?
  565.     {
  566.         struct Process *proc = (struct Process *) FindTask( NULL);
  567.         char *lower;
  568.  
  569.         if ( !proc -> pr_CLI)
  570.         {
  571.             WaitPort( &proc -> pr_MsgPort);
  572.             WBenchMsg = GetMsg( &proc -> pr_MsgPort);
  573.         }
  574.         else
  575.         {
  576.             WBenchMsg = 0;
  577.         }
  578.  
  579.         // Lower Kickstart 2.0 (beta)?
  580.         _Kick1 = _Exec_Base -> LibNode . lib_Version < 36 ? TRUE : FALSE;
  581.  
  582.         _DOS_Base = DOSBase = (struct DosLibrary *) OpenLibrary( DOSName, 33);
  583.         if ( !_DOS_Base || _Kick1)
  584.             exitNR( 122);
  585.  
  586.         if ( !(_Exec_Base -> AttnFlags & AFF_68020))    // At least a 68020 processor?
  587.             exitNR( 105);
  588.  
  589.         // Do we have at least 4000 bytes of stack available? - NOTE: the variable proc is on
  590.         // the top of the stack so using this address is the current upper bound of the stack!
  591.         lower = proc->pr_Task.tc_SPLower;
  592.         if ( (proc->pr_Task.tc_Node.ln_Type == NT_PROCESS) && proc->pr_CLI)
  593.             lower = (char *) proc->pr_ReturnAddr + sizeof( ULONG) - *(ULONG *) proc->pr_ReturnAddr;
  594.         if ( ((char *) &proc - lower) < 4000)
  595.             exitNR( 217);
  596.  
  597.         if (WBenchMsg)
  598.         {
  599.             _wb_parse( (struct WBStartup *) WBenchMsg);
  600.             if ( !_arg_lin)
  601.                 exitNR( 103);    // Fail: no mem
  602.         }
  603.         else
  604.         {
  605.             _cli_parse( proc, parlen, parameter);
  606.             if ( !_arg_lin)
  607.                 exitNR( 103);
  608.         }
  609.  
  610.         // Try detach from CLI, if it fails, go on normal
  611.         if (WBenchMsg || !LaunchStartup())
  612.         {
  613.             CallMainPRG();
  614.         }
  615.     }
  616.     else    // ...we're the task that detached off CLI!
  617.     {
  618.         // Wait for parent
  619.         ObtainSemaphore(&_AsyncSem);
  620.         // Release lock
  621.         ReleaseSemaphore(&_AsyncSem);
  622.         // Main loop
  623.         CallMainPRG();
  624.     }
  625. }
  626.  
  627. // ################################################################
  628.